#include "SehnsuchtPrecompiled.hxx"
#include "SehnsuchtDirectXBase.hxx"


Sehnsucht::DirectXBase::DirectXBase() : 
#ifdef REIGN_METRO
	m_bWindowSizeChangeInProgress(false),
#endif
	m_fDpi(-1.0f)
{
	using namespace Sehnsucht;
}

void Sehnsucht::DirectXBase::Initialize( 
	_In_ DirectXBase::CoreWindowPtr pWindow, // Window wrapper pointer
	_In_ Reign::Standard::Float32 fDpi, // Display DPI
	_In_opt_ DirectXBase::DirectXDriver eDriver
	)
{
	using namespace Sehnsucht;

	m_Window = pWindow;
	m_eDriver = eDriver;
	CreateDeviceIndependentResources();
	CreateDeviceResources( m_eDriver );
	SetDpi(fDpi);
}

void Sehnsucht::DirectXBase::HandleDeviceLost()
{
	using namespace Sehnsucht;

	auto fDpi = m_fDpi;
	m_fDpi = (-1.0f);

	Reign::Standard::ZeroValue(&m_WindowBounds);
	m_SwapChain3 = nullptr;

	CreateDeviceResources( m_eDriver );
	SetDpi(m_fDpi);

}

void Sehnsucht::DirectXBase::ValidateDevice()
{
	using namespace Sehnsucht;

	// The D3D Device is no longer valid if the default adapter changes or if
    // the device has been removed.

    // First, get the information for the adapter related to the current device.

	Microsoft::WRL::ComPtr<IDXGIDevice1> DxgiDevice;
	Microsoft::WRL::ComPtr<IDXGIAdapter> DeviceAdapter;
	DXGI_ADAPTER_DESC DeviceDesc;

	Sehnsucht::ThrowIfFailed(m_Device3.As(&DeviceAdapter));
	Sehnsucht::ThrowIfFailed(DeviceAdapter.As(&DeviceAdapter));
	Sehnsucht::ThrowIfFailed(DeviceAdapter->GetDesc(&DeviceDesc));

	// Next, get the information for the default adapter.

    Microsoft::WRL::ComPtr<IDXGIFactory2> DxgiFactory;
    Microsoft::WRL::ComPtr<IDXGIAdapter1> CurrentAdapter;
    DXGI_ADAPTER_DESC CurrentDesc;

    Sehnsucht::ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&DxgiFactory)));
    Sehnsucht::ThrowIfFailed(DxgiFactory->EnumAdapters1(0, &CurrentAdapter));
    Sehnsucht::ThrowIfFailed(CurrentAdapter->GetDesc(&CurrentDesc));

    // If the adapter LUIDs don't match, or if the device reports that it has been removed,
    // a new D3D device must be created.

    if ((DeviceDesc.AdapterLuid.LowPart != CurrentDesc.AdapterLuid.LowPart) ||
        (DeviceDesc.AdapterLuid.HighPart != CurrentDesc.AdapterLuid.HighPart) ||
        (FAILED(m_Device3->GetDeviceRemovedReason())))
    {
        // Release references to resources related to the old device.
        DxgiDevice = nullptr;
        DeviceAdapter = nullptr;

        // Create a new device and swap chain.
        HandleDeviceLost();
    }
}

void Sehnsucht::DirectXBase::CreateDeviceIndependentResources()
{
	using namespace Sehnsucht;

#ifndef REIGN_PHONE

	D2D1_FACTORY_OPTIONS Options;
    Reign::Standard::ZeroValue(&Options);

#if defined(_DEBUG)
    // If the project is in a debug build, enable Direct2D debugging via SDK Layers.
    Options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

    Sehnsucht::ThrowIfFailed(
        D2D1CreateFactory(
            D2D1_FACTORY_TYPE_SINGLE_THREADED,
            __uuidof(ID2D1Factory1),
            &Options,
            &m_Factory2
            )
        );

    Sehnsucht::ThrowIfFailed(
        DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(IDWriteFactory),
            &m_DwFactory2
            )
        );

    Sehnsucht::ThrowIfFailed(
        CoCreateInstance(
            CLSID_WICImagingFactory,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_WicFactory2)
            )
        );

#endif
}


// These are the resources that depend on the device.
void Sehnsucht::DirectXBase::CreateDeviceResources( _In_opt_ D3D_DRIVER_TYPE eDriver )
{
	using namespace Sehnsucht;

    // This flag adds support for surfaces with a different color channel ordering
    // than the API default. It is required for compatibility with Direct2D.
    UINT CreationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
    // If the project is in a debug build, enable debugging via SDK Layers with this flag.
    CreationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    // This array defines the set of DirectX hardware feature levels this app will support.
    // Note the ordering should be preserved.
    // Don't forget to declare your application's minimum required feature level in its
    // description.  All applications are assumed to support 9.1 unless otherwise stated.
    D3D_FEATURE_LEVEL FeatureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1
    };

    // Create the Direct3D 11 API device object and a corresponding context.
    Microsoft::WRL::ComPtr<ID3D11Device> Device;
    Microsoft::WRL::ComPtr<IDXGIDevice> DxgiDevice;
    Microsoft::WRL::ComPtr<ID3D11DeviceContext> Context;
    Sehnsucht::ThrowIfFailed(
        D3D11CreateDevice(
            nullptr,                    // Specify nullptr to use the default adapter.
            eDriver,
            0,
            CreationFlags,              // Set debug and Direct2D compatibility flags.
            FeatureLevels,              // List of feature levels this app can support.
            ARRAYSIZE(FeatureLevels),
            D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for Metro style apps.
            &Device,                    // Returns the Direct3D device created.
            &m_FeatureLevel3,           // Returns feature level of device created.
            &Context                    // Returns the device immediate context.
            )
        );

    // Get the Direct3D 11.1 API device and context interfaces.
    Sehnsucht::ThrowIfFailed(
        Device.As(&m_Device3)
        );

    Sehnsucht::ThrowIfFailed(
        Context.As(&m_Context3)
        );

    // Get the underlying DXGI device of the Direct3D device.
    Sehnsucht::ThrowIfFailed(
        m_Device3.As(&DxgiDevice)
        );

#ifndef REIGN_PHONE

    // Create the Direct2D device object and a corresponding context.
    Sehnsucht::ThrowIfFailed(
        m_Factory2->CreateDevice(
			DxgiDevice.Get(), 
			&m_Device2
			)
        );

    Sehnsucht::ThrowIfFailed(
        m_Device2->CreateDeviceContext(
            D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS,
            &m_Context2
            )
        );

#endif
}

// Helps track the DPI in the helper class.
// This is called in the dpiChanged event handler in the view class.
void Sehnsucht::DirectXBase::SetDpi( _In_ Reign::Standard::Float32 fDpi )
{
	using namespace Sehnsucht;

    if (fDpi != m_fDpi)
    {
        // Save the DPI of this display in our class.
        m_fDpi = fDpi;

#ifndef REIGN_PHONE
        // Update Direct2D's stored DPI.
        m_Context2->SetDpi(m_fDpi, m_fDpi);
#endif

        // Often a DPI change implies a window size change. In some cases Windows will issue
        // both a size changed event and a DPI changed event. In this case, the resulting bounds
        // will not change, and the window resize code will only be executed once.
        UpdateForWindowSizeChange();
    }
}

// This routine is called in the event handler for the view SizeChanged event.
void Sehnsucht::DirectXBase::UpdateForWindowSizeChange()
{
	using namespace Sehnsucht;

#ifndef REIGN_DESKTOP

	auto NewDpi = Windows::Graphics::Display::DisplayProperties::LogicalDpi;
	auto NewHeight = m_Window->Bounds.Height;
	auto OldHeight = m_WindowBounds.Height;
	auto NewWidth = m_Window->Bounds.Width;
	auto OldWidth = m_WindowBounds.Width;
    
#else

	auto NewDpi = .0f;
	{
		auto screenHandle = GetDC( m_Window );
		NewDpi = (Float32)(GetDeviceCaps( screenHandle, LOGPIXELSX ));
		AutoDebugVariable(YDpi) (Float32)(GetDeviceCaps( screenHandle, LOGPIXELSY ));
		ReleaseDC( m_Window, screenHandle );
	}

	RECT ClientBounds = { 0 };
	GetClientRect(m_Window, &ClientBounds);
	/*m_WindowBounds.bottom = (float)ClientBounds.bottom;
	m_WindowBounds.right = (float)ClientBounds.right;
	m_WindowBounds.left = (float)ClientBounds.left;
	m_WindowBounds.top = (float)ClientBounds.top;*/

	auto OldWidth = m_WindowBounds.right - m_WindowBounds.left;
	auto OldHeight = m_WindowBounds.bottom - m_WindowBounds.top;
	auto NewHeight = ClientBounds.bottom - ClientBounds.top;
	auto NewWidth = ClientBounds.right - ClientBounds.left;

#endif

    // Only handle window size changed if there is no pending DPI change.
    if (m_fDpi != NewDpi)
    {
        return;
    }

	if (NewWidth  != OldWidth ||
        NewHeight != OldHeight)
    {

#ifndef REIGN_PHONE // !Phone 
        m_Context2->SetTarget(nullptr);
        m_TargetBitmap2 = nullptr;
#endif // All

#ifdef REIGN_METRO // Metro
        m_bWindowSizeChangeInProgress = true;
#endif // All

        m_RenderTargetView3 = nullptr;
        m_DepthStencilView3 = nullptr;

        CreateWindowSizeDependentResources();
    }
}

// Allocate all memory resources that change on a window SizeChanged event.
void Sehnsucht::DirectXBase::CreateWindowSizeDependentResources()
{
	using namespace Sehnsucht;

    // Store the window bounds so the next time we get a SizeChanged event we can
    // avoid rebuilding everything if the size is identical.

#ifdef REIGN_DESKTOP

	RECT ClientBounds; Reign::Standard::ZeroValue(&ClientBounds);
	GetClientRect(m_Window, &ClientBounds);
	m_WindowBounds.bottom = (float)ClientBounds.bottom;
	m_WindowBounds.right = (float)ClientBounds.right;
	m_WindowBounds.left = (float)ClientBounds.left;
	m_WindowBounds.top = (float)ClientBounds.top;

#else

    m_WindowBounds = m_Window->Bounds;

#endif

    if (m_SwapChain3 != nullptr)
    {
        // If the swap chain already exists, resize it.
        HRESULT Handle = m_SwapChain3->ResizeBuffers(
			2, 0, 0, SEHNSUCHT_DEFAULT_DXGI_FORMAT, 0
			);

        if (Handle == DXGI_ERROR_DEVICE_REMOVED)
        {
            // If the device was removed for any reason, a new device and swapchain will need to be created.
            HandleDeviceLost();

            // Everything is set up now. Do not continue execution of this method.
            return;
        }
        else
        {
            Sehnsucht::ThrowIfFailed(Handle);
        }
    }
    else
    {
        // Otherwise, create a new one using the same adapter as the existing Direct3D device.

        DXGI_SWAP_CHAIN_DESC1 SwapChainDesc = { 0 };					// Use automatic sizing. Dont use stereo. No additional flags.
        SwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;	// Use render target.
        SwapChainDesc.Format = SEHNSUCHT_DEFAULT_DXGI_FORMAT;			// This is the most common swap chain format.
        SwapChainDesc.SampleDesc.Count = 1;								// Don't use multi-sampling.
        SwapChainDesc.SampleDesc.Quality = 0;

#ifdef REIGN_PHONE
        SwapChainDesc.BufferCount = 1;									// Dont use double-buffering to minimize latency.
        SwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;			// All Phone apps must use this SwapEffect.
        SwapChainDesc.Scaling = DXGI_SCALING_STRETCH;					// All Phone apps must use this scaling.
#else
        SwapChainDesc.BufferCount = 2;									// Use double-buffering to minimize latency.
        SwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;	// All Metro style apps must use this SwapEffect.
        SwapChainDesc.Scaling = DXGI_SCALING_NONE;						// Dont use scaling
#endif

        Microsoft::WRL::ComPtr<IDXGIDevice1> DxgiDevice;
        Sehnsucht::ThrowIfFailed(
            m_Device3.As(&DxgiDevice)
            );

        Microsoft::WRL::ComPtr<IDXGIAdapter> DxgiAdapter;
        Sehnsucht::ThrowIfFailed(
            DxgiDevice->GetAdapter(&DxgiAdapter)
            );

        Microsoft::WRL::ComPtr<IDXGIFactory2> dxgiFactory;
        Sehnsucht::ThrowIfFailed(
            DxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory))
            );

#ifdef REIGN_DESKTOP

		DXGI_SWAP_CHAIN_FULLSCREEN_DESC FullscreenDesc;
		FullscreenDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
		FullscreenDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
		FullscreenDesc.RefreshRate.Denominator = 1;
		FullscreenDesc.RefreshRate.Numerator = 60;
		FullscreenDesc.Windowed = TRUE;

		Sehnsucht::ThrowIfFailed(
			dxgiFactory->CreateSwapChainForHwnd(
				m_Device3.Get(),
				m_Window,
				&SwapChainDesc, &FullscreenDesc, 
				nullptr,
				&m_SwapChain3
				)
			);
#else
       auto pWindow = m_Window.Get();
       Sehnsucht::ThrowIfFailed(
			dxgiFactory->CreateSwapChainForCoreWindow(
				m_Device3.Get(),
				reinterpret_cast<IUnknown*>(pWindow),
				&SwapChainDesc, nullptr,
				&m_SwapChain3
				)
			);
#endif
        // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and
        // ensures that the application will only render after each VSync, minimizing power consumption.
        Sehnsucht::ThrowIfFailed(
            DxgiDevice->SetMaximumFrameLatency(1)
            );
    }

    // Create a Direct3D render target view of the swap chain back buffer.
    Microsoft::WRL::ComPtr<ID3D11Texture2D> BackBuffer;
    Sehnsucht::ThrowIfFailed(
        m_SwapChain3->GetBuffer(
			0, IID_PPV_ARGS(&BackBuffer)
			)
        );

    Sehnsucht::ThrowIfFailed(
        m_Device3->CreateRenderTargetView(
            BackBuffer.Get(), nullptr,
            &m_RenderTargetView3
            )
        );

    // Cache the rendertarget dimensions in our helper class for convenient use.
    D3D11_TEXTURE2D_DESC BackBufferDesc = { 0 };
    BackBuffer->GetDesc(&BackBufferDesc);

#ifdef REIGN_DESKTOP
    m_RenderTargetSize.width  = static_cast<float>(BackBufferDesc.Width);
    m_RenderTargetSize.height = static_cast<float>(BackBufferDesc.Height);
#else
    m_RenderTargetSize.Width  = static_cast<float>(BackBufferDesc.Width);
    m_RenderTargetSize.Height = static_cast<float>(BackBufferDesc.Height);
#endif

    // Create a depth stencil view for use with 3D rendering if needed.
    CD3D11_TEXTURE2D_DESC DepthStencilDesc(
        DXGI_FORMAT_D24_UNORM_S8_UINT,
        BackBufferDesc.Width, BackBufferDesc.Height,
        1, 1, D3D11_BIND_DEPTH_STENCIL
        );

    Microsoft::WRL::ComPtr<ID3D11Texture2D> DepthStencil;
    Sehnsucht::ThrowIfFailed(
        m_Device3->CreateTexture2D(
            &DepthStencilDesc, nullptr,
            &DepthStencil
            )
        );

    CD3D11_DEPTH_STENCIL_VIEW_DESC ViewDesc(
		D3D11_DSV_DIMENSION_TEXTURE2D
		);

    Sehnsucht::ThrowIfFailed(
        m_Device3->CreateDepthStencilView(
            DepthStencil.Get(), &ViewDesc,
            &m_DepthStencilView3
            )
        );

    // Set the 3D rendering viewport to target the entire window.
    CD3D11_VIEWPORT Viewport( 
		0.0f, 0.0f,
        static_cast<float>(BackBufferDesc.Width),
        static_cast<float>(BackBufferDesc.Height)
        );

    m_Context3->RSSetViewports(1, &Viewport);

#ifndef REIGN_PHONE

    // Create a Direct2D target bitmap associated with the
    // swap chain back buffer and set it as the current target.
    D2D1_BITMAP_PROPERTIES1 BitmapProperties =
        D2D1::BitmapProperties1(
            D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
            D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
            m_fDpi,
            m_fDpi
            );

    Microsoft::WRL::ComPtr<IDXGISurface> DxgiBackBuffer;
    Sehnsucht::ThrowIfFailed(
        m_SwapChain3->GetBuffer(
			0, IID_PPV_ARGS(&DxgiBackBuffer)
			)
        );

    Sehnsucht::ThrowIfFailed(
        m_Context2->CreateBitmapFromDxgiSurface(
            DxgiBackBuffer.Get(),
            &BitmapProperties,
            &m_TargetBitmap2
            )
        );

    m_Context2->SetTarget(
		m_TargetBitmap2.Get()
		);

    // Grayscale text anti-aliasing is recommended for all Metro style apps.
    m_Context2->SetTextAntialiasMode(
		D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE
		);

#endif
}

// Method to deliver the final image to the display.
void Sehnsucht::DirectXBase::Present()
{
	using namespace Sehnsucht;

	// Reduce the number of the unused 11.1 features

#if 0 //ndef REIGN_PHONE

    // The application may optionally specify "dirty" or "scroll" rects to improve efficiency
    // in certain scenarios.  In this sample, however, we do not utilize those features.
    DXGI_PRESENT_PARAMETERS parameters = {0};
    /*parameters.DirtyRectsCount = 0u;
    parameters.pDirtyRects = nullptr;
    parameters.pScrollRect = nullptr;
    parameters.pScrollOffset = nullptr;*/

    // The first argument instructs DXGI to block until VSync, putting the application
    // to sleep until the next VSync. This ensures we don't waste any cycles rendering
    // frames that will never be displayed to the screen.
    HResult Handle = m_SwapChain3->Present1(1, 0, &parameters);

#else

    HResult Handle = m_SwapChain3->Present(1, 0);

#endif

    // Discard the contents of the render target.
    // This is a valid operation only when the existing contents will be entirely
    // overwritten. If dirty or scroll rects are used, this call should be removed.
    m_Context3->DiscardView(m_RenderTargetView3.Get());

    // Discard the contents of the depth stencil.
    m_Context3->DiscardView(m_DepthStencilView3.Get());

    // If the device was removed either by a disconnect or a driver upgrade, we
    // must recreate all device resources.
    if (Handle == DXGI_ERROR_DEVICE_REMOVED)
    {
        HandleDeviceLost();
    }
    else
    {
        Sehnsucht::ThrowIfFailed(Handle);
    }

#ifdef REIGN_METRO
    if (m_bWindowSizeChangeInProgress)
    {
        // A window size change has been initiated and the app has just completed presenting
        // the first frame with the new size. Notify the resize manager so we can short
        // circuit any resize animation and prevent unnecessary delays.
        Windows::UI::Core::CoreWindowResizeManager::GetForCurrentView()->NotifyLayoutCompleted();
        m_bWindowSizeChangeInProgress = false;
    }
#endif
}